use libc::size_t;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;

#[link(name="xdelta3")]
unsafe extern "C" {
    fn xd3_encode_memory (
            input:*const libc::__u8,
            input_size:libc::size_t,
            source:*const libc::__u8,
            source_size:libc::size_t,
            output:*mut libc::__u8,
            output_size:*mut size_t,
            output_size_max:libc::size_t,
            flags:libc::c_int
    ) -> libc::c_int;
fn xd3_decode_memory (
        input:*const libc::__u8,
        input_size:libc::size_t,
        source:*const libc::__u8,
        source_size:libc::size_t,
        output:*mut libc::__u8,
        output_size:*mut size_t,
        output_size_max:libc::size_t,
        flags:libc::c_int
    ) -> libc::c_int;
}

#[derive(Debug, Clone, FromPrimitive)]
pub enum Xd3Error{
    /* An application must be prepared to handle these five return
     * values from either xd3_encode_input or xd3_decode_input, except
     * in the case of no-source compression, in which case XD3_GETSRCBLK
     * is never returned.  More detailed comments for these are given in
     * xd3_encode_input and xd3_decode_input comments, below. */
    Xd3Input     = -17703, /* need input */
    Xd3Output    = -17704, /* have output */
    Xd3Getsrcblk = -17705, /* need a block of source input (with no
                 * xd3_getblk function), a chance to do
                 * non-blocking read. */
    Xd3Gotheader = -17706, /* (decode-only) after the initial VCDIFF &
                   first window header */
    Xd3Winstart  = -17707, /* notification: returned before a window is
                 * processed, giving a chance to
                 * XD3_SKIP_WINDOW or not XD3_SKIP_EMIT that
                 * window. */
    Xd3Winfinish  = -17708, /* notification: returned after
                    encode/decode & output for a window */
    Xd3Toofarback = -17709, /* (encoder only) may be returned by
                    getblk() if the block is too old */
    Xd3Internal   = -17710, /* internal error */
    Xd3Invalid    = -17711, /* invalid config */
    Xd3InvalidInput = -17712, /* invalid input/decoder error */
    Xd3Nosecond    = -17713, /* when secondary compression finds no
                     improvement. */
    Xd3Unimplemented = -17714  /* currently VCD_TARGET, VCD_CODETABLE */
}

impl std::fmt::Display for Xd3Error { 
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Debug::fmt(self, f)
    }
}

impl std::error::Error for Xd3Error {  }

pub struct Xd3Config {
    inner:u32,
}

impl Xd3Config {
    pub fn new() -> Self {
        Self { inner:0, }
    }
    // used by VCDIFF tools
    pub fn just_hdr(&mut self) { self.inner |= 1 << 1; }

    // used by VCDIFF tools
    pub fn skip_window(&mut self) { self.inner |= 1 << 2; }

    // used by VCDIFF tools
    pub fn skip_emit(&mut self) { self.inner |= 1 << 3; }

    // flush the stream buffer toprepare for xd3_stream_close().
    pub fn flush(&mut self) { self.inner |= 1 << 4; }

    // use DJW static huffman
    pub fn sec_djw(&mut self) { self.inner |= 1 << 5; }

    // use FGK adaptive huffman
    pub fn sec_fgk(&mut self) { self.inner |= 1 << 6; }

    // use LZMA secondary
    pub fn sec_lzma(&mut self) { self.inner |= 1 << 24; }

    pub fn sec_type(&mut self) { self.inner |= (1 << 5) | (1 << 6) | (1 << 24); }

    // disable secondary compression of the data section
    pub fn sec_no_data(&mut self) { self.inner |= 1 << 7; }

    // disable secondary compression of the inst section
    pub fn sec_no_inst(&mut self) { self.inner |= 1 << 8; }

    // disable secondary compression of the addr section
    pub fn sec_no_addr(&mut self) { self.inner |= 1 << 9; }

    // disable secondary compression of the (data,inst,addr) section
    pub fn sec_no_all(&mut self) { self.inner |= (1 << 7) | (1 << 8) | (1 << 9); }

    // enable checksum computation in the encoder.
    pub fn adler32(&mut self) { self.inner |= 1 << 10; }

    // enable checksum verification in the decoder.
    pub fn adler32_no_vertification(&mut self) { self.inner |= 1 << 11; }

    //disable ordinary data compression feature, only search the source, not the target.
    pub fn no_compress(&mut self) { self.inner |= 1 << 13; }

    //disable the "1.5-pass algorithm", instead use greedy matching.  Greedy is off by default
    pub fn be_greedy(&mut self) { self.inner |= 1 << 14; }
    
    //used by "recode".
    pub fn adler32_recode(&mut self) { self.inner |= 1 << 15; }

    const XD3_COMPLEVEL_SHIFT:u32 = 20;  /* bits 20 - 23 */

    /* 4 bits to set the compression level the same as the command-line
    * setting -1 through -9 (-0 corresponds to the XD3_NOCOMPRESS flag,
    * and is independent of compression level).  This is for
    * convenience, especially with xd3_encode_memory(). */
    pub fn compression_level(&mut self, level:u32) -> bool { 
        if level > 9 { return false; }
        self.inner |= level << Self::XD3_COMPLEVEL_SHIFT; 
        return true;
    }

    pub fn encode(&self, input:&[u8], src:&[u8], output:&mut [u8], valid_output_size:&mut usize, output_size_max:usize) -> Result<(), Xd3Error> {
        let err_code = unsafe { xd3_encode_memory(&input[0] as *const u8, input.len(), &src[0] as *const u8, src.len(), 
                &mut output[0] as *mut u8, valid_output_size as *mut usize, output_size_max as libc::size_t, self.inner as libc::c_int) };
        if err_code == 0 { return Ok(()) }
        else { Err(FromPrimitive::from_i32(err_code).unwrap()) }
    }

    pub fn decode(&self, input:&[u8], src:&[u8], output:&mut [u8], valid_output_size:&mut usize, output_size_max:usize) -> Result<(), Xd3Error> {
        let err_code = unsafe { xd3_decode_memory(&input[0] as *const u8, input.len(), &src[0] as *const u8, src.len(), 
                &mut output[0] as *mut u8, valid_output_size as *mut usize, output_size_max as libc::size_t, self.inner as libc::c_int) };
        if err_code == 0 { return Ok(()) }
        else { Err(FromPrimitive::from_i32(err_code).unwrap()) }
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let xd3_config = Xd3Config::new();
        let input = &[0, 1, 2, 3, 4, 5, 6];
        let src = &[0, 1, 2, 3, 4, 5, 6];
        let mut output = vec![0;24];
        let mut valid_output_size = 0;
        let output_size_max = (input.len() + src.len()) * 2;
        xd3_config.encode(input, src, &mut output, &mut valid_output_size, output_size_max).expect("encode error");

        let delta = &output[0..valid_output_size];
        let mut decode_output = vec![7;input.len()];
        let mut valid_decode_output_size = 0;
        xd3_config.decode(delta, src, &mut decode_output, &mut valid_decode_output_size, input.len()).expect("decode error");
        assert_eq!(decode_output, input);
    }
}
